/*
# Copyright (c) 2024 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
*/

#include <pjmedia-videodev/videodev_imp.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/math.h>
#include <pj/os.h>

#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \
    defined(PJMEDIA_VIDEO_DEV_HAS_OHOS) && \
    PJMEDIA_VIDEO_DEV_HAS_OHOS != 0

#ifdef __cplusplus
extern "C" {
#endif
#include "util.h"
#ifdef __cplusplus
}
#endif
#include "ohos_video_receiver.h"

#define THIS_FILE "ohos_video_dev.cpp"

/* Default video params */
#define DEFAULT_CLOCK_RATE 90000
#define DEFAULT_WIDTH 1920
#define DEFAULT_HEIGHT 1080
#define DEFAULT_FPS 15
#define ALIGN16(x) ((((x) + 15) >> 4) << 4)

/* Format map info */
typedef struct ohos_fmt_map {
    pjmedia_format_id fmt_id;
    pj_uint32_t ohos_fmt_id;
} ohos_fmt_map;

/* Device info */
typedef struct ohos_video_dev_info {
    pjmedia_vid_dev_info info;   /**< Base info         */
    unsigned dev_idx;            /**< Original dev ID   */
    pj_bool_t facing;            /**< Front/back camera?*/
    unsigned sup_size_cnt;       /**< # of supp'd size  */
    pjmedia_rect_size *sup_size; /**< Supported size    */
    unsigned sup_fps_cnt;        /**< # of supp'd FPS   */
    pjmedia_rect_size *sup_fps;  /**< Supported FPS     */
    pj_bool_t has_yv12;          /**< Support YV12?     */
    pj_bool_t has_nv21;          /**< Support NV21?     */
    pj_bool_t forced_i420;       /**< Support I420 with */
} ohos_video_dev_info;

/* Video factory */
typedef struct ohos_video_factory {
    pjmedia_vid_dev_factory base; /**< Base factory      */
    pj_pool_t *pool;              /**< Memory pool       */
    pj_pool_factory *pf;          /**< Pool factory      */
    pj_pool_t *dev_pool;    /**< Device list pool  */
    unsigned dev_count;     /**< Device count      */
    ohos_video_dev_info *dev_info; /**< Device info list  */
    VideoFrameReceiver *recevider;
} ohos_video_factory;

/* Video stream. */
typedef struct ohos_video_stream {
    pjmedia_vid_dev_stream base; /**< Base stream       */
    pjmedia_vid_dev_param param; /**< Settings          */
    pj_pool_t *pool;             /**< Memory pool       */
    ohos_video_factory *factory; /**< Factory           */
    pjmedia_vid_dev_cb vid_cb; /**< Stream callback   */
    void *user_data;           /**< Application data  */
    pj_bool_t is_running;      /**< Stream running?   */
    pj_timestamp frame_ts;    /**< Current timestamp */
    unsigned ts_inc;          /**< Timestamp interval*/
    unsigned convert_to_i420; /**< Need to convert to I420?
    /** NV21/YV12 -> I420 Conversion buffer  */
    pj_uint8_t *convert_buf;
    pjmedia_rect_size cam_size;
    /** Converter to rotate frame  */
    pjmedia_vid_dev_conv conv;
    /** Frame format param for NV21/YV12 -> I420 conversion */
    pjmedia_video_apply_fmt_param vafp;
    pj_bool_t               thread_initialized;
    pj_thread_desc          thread_desc;
    pj_thread_t            *thread;
} ohos_video_stream;


/* Prototypes */
static pj_status_t ohos_factory_init(pjmedia_vid_dev_factory *f);
static pj_status_t ohos_factory_destroy(pjmedia_vid_dev_factory *f);
static pj_status_t ohos_factory_refresh(pjmedia_vid_dev_factory *f);
static unsigned ohos_factory_get_dev_count(pjmedia_vid_dev_factory *f);
static pj_status_t ohos_factory_get_dev_info(pjmedia_vid_dev_factory *f, unsigned index, pjmedia_vid_dev_info *info);
static pj_status_t ohos_factory_default_param(pj_pool_t *pool, pjmedia_vid_dev_factory *f, unsigned index,
                                             pjmedia_vid_dev_param *param);
static pj_status_t ohos_factory_create_stream(pjmedia_vid_dev_factory *f, pjmedia_vid_dev_param *param,
                                             const pjmedia_vid_dev_cb *cb, void *user_data,
                                             pjmedia_vid_dev_stream **p_vid_strm);

static pj_status_t ohos_stream_get_param(pjmedia_vid_dev_stream *strm, pjmedia_vid_dev_param *param);
static pj_status_t ohos_stream_get_cap(pjmedia_vid_dev_stream *strm, pjmedia_vid_dev_cap cap, void *value);
static pj_status_t ohos_stream_set_cap(pjmedia_vid_dev_stream *strm, pjmedia_vid_dev_cap cap, const void *value);
static pj_status_t ohos_stream_start(pjmedia_vid_dev_stream *strm);
static pj_status_t ohos_stream_stop(pjmedia_vid_dev_stream *strm);
static pj_status_t ohos_stream_destroy(pjmedia_vid_dev_stream *strm);


/* Operations */
static pjmedia_vid_dev_factory_op factory_op = {
    &ohos_factory_init,
    &ohos_factory_destroy,
    &ohos_factory_get_dev_count,
    &ohos_factory_get_dev_info,
    &ohos_factory_default_param,
    &ohos_factory_create_stream,
    &ohos_factory_refresh
};

static pjmedia_vid_dev_stream_op stream_op = {
    &ohos_stream_get_param,
    &ohos_stream_get_cap,
    &ohos_stream_set_cap,
    &ohos_stream_start,
    NULL,
    NULL,
    &ohos_stream_stop,
    &ohos_stream_destroy
};

static void OnFrameCallBack(void *steam, void *buff, int length) {
    ohos_video_stream *strm = (ohos_video_stream *)(intptr_t)steam;
    if (strm->thread_initialized == 0 || !pj_thread_is_registered()) {
        pj_status_t status;
        pj_bzero(strm->thread_desc, sizeof(pj_thread_desc));
        status = pj_thread_register("ohos_cam", strm->thread_desc,
                                    &strm->thread);
        if (status != PJ_SUCCESS)
            return;
        strm->thread_initialized = 1;
        PJ_LOG(5,(THIS_FILE, "Harmony camera thread registered"));
    }
    pjmedia_frame f;
    pj_uint8_t *Y, *U, *V;
    pj_status_t status;
    void *frame_buf, *data_buf;

    strm->frame_ts.u64 += strm->ts_inc;
    if (!strm->vid_cb.capture_cb)
        return;

    f.type = PJMEDIA_FRAME_TYPE_VIDEO;
    f.size = length;
    f.timestamp.u64 = strm->frame_ts.u64;
    f.buf = data_buf = buff;

    Y = (pj_uint8_t *)f.buf;
    U = Y + strm->vafp.plane_bytes[0];
    V = U + strm->vafp.plane_bytes[1];

    /* Convert NV21 -> I420, i.e: separate V/U interleaved data plane
     * into U & V planes.
     */
    if (strm->convert_to_i420 == 1) {
        pj_uint8_t *src = U;
        pj_uint8_t *dst_u = U;
        pj_uint8_t *end_u = U + strm->vafp.plane_bytes[1];
        pj_uint8_t *dst_v = strm->convert_buf;
        while (dst_u < end_u) {
            *dst_v++ = *src++;
            *dst_u++ = *src++;
        }
        pj_memcpy(V, strm->convert_buf, strm->vafp.plane_bytes[2]);
    }

    /* Convert YV12 -> I420, i.e: swap U & V planes. We also need to
     * strip out padding, if any.
     */
    else if (strm->convert_to_i420 == 2) {
        int y_stride = ALIGN16(strm->vafp.size.w);
        int uv_stride = ALIGN16(strm->vafp.size.w / 2);

        /* Strip out Y padding */
        if (y_stride > strm->vafp.size.w) {
            int i;
            pj_uint8_t *src = Y + y_stride;
            pj_uint8_t *dst = Y + strm->vafp.size.w;

            for (i = 1; i < strm->vafp.size.h; ++i) {
                memmove(dst, src, strm->vafp.size.w);
                src += y_stride;
                dst += strm->vafp.size.w;
            }
        }

        /* Swap U & V planes */
        if (uv_stride == strm->vafp.size.w / 2) {

            /* No padding, note Y plane should be no padding too! */
            pj_assert(y_stride == strm->vafp.size.w);
            pj_memcpy(strm->convert_buf, U, strm->vafp.plane_bytes[1]);
            pj_memmove(U, V, strm->vafp.plane_bytes[1]);
            pj_memcpy(V, strm->convert_buf, strm->vafp.plane_bytes[1]);

        } else if (uv_stride > strm->vafp.size.w / 2) {

            /* Strip & copy V plane into conversion buffer */
            pj_uint8_t *src = Y + y_stride * strm->vafp.size.h;
            pj_uint8_t *dst = strm->convert_buf;
            unsigned dst_stride = strm->vafp.size.w / 2;
            int i;
            for (i = 0; i < strm->vafp.size.h / 2; ++i) {
                memmove(dst, src, dst_stride);
                src += uv_stride;
                dst += dst_stride;
            }

            /* Strip U plane */
            dst = U;
            for (i = 0; i < strm->vafp.size.h / 2; ++i) {
                memmove(dst, src, dst_stride);
                src += uv_stride;
                dst += dst_stride;
            }

            /* Get V plane data from conversion buffer */
            pj_memcpy(V, strm->convert_buf, strm->vafp.plane_bytes[2]);
        }
    }

    status = pjmedia_vid_dev_conv_resize_and_rotate(&strm->conv, f.buf, &frame_buf);
    if (status == PJ_SUCCESS) {
        f.buf = frame_buf;
    }

    (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &f);
}

/*
 * Init ohos audio driver.
 */
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Init ohos video driver.
 */
pjmedia_vid_dev_factory *pjmedia_ohos_and_factory(pj_pool_factory *pf) {
    ohos_video_factory *f;
    pj_pool_t *pool;

    pool = pj_pool_create(pf, "ohos_video", 512, 512, NULL);
    f = PJ_POOL_ZALLOC_T(pool, ohos_video_factory);
    f->pf = pf;
    f->pool = pool;
    f->base.op = &factory_op;
    f->dev_pool = pj_pool_create(pf, "ohos_video_dev", 512, 512, NULL);

    return &f->base;
}
#ifdef __cplusplus
}
#endif

/* API: init factory */
static pj_status_t ohos_factory_init(pjmedia_vid_dev_factory *ff) {
     PJ_LOG(3, (THIS_FILE, "ohos_factory_init start"));
    pj_status_t status;
    FrameCallBack frameCallBack;
    frameCallBack.onFrameCallBack = OnFrameCallBack;
    auto videoFrameReceiver = VideoFrameReceiver::Create(DEFAULT_WIDTH, DEFAULT_HEIGHT, frameCallBack);
    if (videoFrameReceiver) {
        ohos_video_factory *f = (ohos_video_factory *)ff;
        f->recevider = videoFrameReceiver;
    }
    status = ohos_factory_refresh(ff);
    if (status != PJ_SUCCESS)
        return status;
    PJ_LOG(3, (THIS_FILE, "ohos_factory_init success"));
    return PJ_SUCCESS;
}

/* API: destroy factory */
static pj_status_t ohos_factory_destroy(pjmedia_vid_dev_factory *ff) {
     PJ_LOG(3, (THIS_FILE, "ohos_factory_destroy "));
    ohos_video_factory *f = (ohos_video_factory *)ff;
    pj_pool_safe_release(&f->dev_pool);
    pj_pool_safe_release(&f->pool);
    f->recevider->destroy();
    return PJ_SUCCESS;
}

/* API: refresh the list of devices */
static pj_status_t ohos_factory_refresh(pjmedia_vid_dev_factory *ff) {
    ohos_video_factory *f = (ohos_video_factory *)ff;
    pj_status_t status = PJ_SUCCESS;

    pj_bool_t with_attach, found_front = PJ_FALSE;
    int i, dev_count = 0;

    /* Clean up device info and pool */
    f->dev_count = 0;
    pj_pool_reset(f->dev_pool);
    dev_count = f->recevider->getCameraCount();
     PJ_LOG(3, (THIS_FILE, "ohos_factory_refresh dev_count:%d",dev_count));
    if (dev_count < 0) {
        PJ_LOG(3, (THIS_FILE, "Failed to get camera count"));
        status = PJMEDIA_EVID_SYSERR;
        goto on_return;
    }

    /* Start querying device info */
    f->dev_info = (ohos_video_dev_info *)pj_pool_calloc(f->dev_pool, dev_count, sizeof(ohos_video_dev_info));

    for (i = 0; i < dev_count; i++) {
        ohos_video_dev_info *adi = &f->dev_info[f->dev_count];
        pjmedia_vid_dev_info *vdi = &adi->info;

        int facing, max_fmt_cnt = PJMEDIA_VID_DEV_INFO_FMT_CNT;

        /* jdev_info = PjCameraInfo::GetCameraInfo(i) */
        auto info = f->recevider->getCamera(i);

        /* Get camera facing: 1=back 2=front */
        facing = info.cameraPosition;

        /* Set device ID, direction, and has_callback info */
        adi->dev_idx = i;
        vdi->id = f->dev_count;
        vdi->dir = PJMEDIA_DIR_CAPTURE;
        vdi->has_callback = PJ_TRUE;
        vdi->caps = PJMEDIA_VID_DEV_CAP_SWITCH | PJMEDIA_VID_DEV_CAP_ORIENTATION;

        /* Set driver & name info */
        pj_ansi_strxcpy(vdi->driver, "Ohos", sizeof(vdi->driver));
        adi->facing = facing;
        if (facing == 1) {
            pj_ansi_strxcpy(vdi->name, "Back camera", sizeof(vdi->name));
        } else if (facing == 2) {
            pj_ansi_strxcpy(vdi->name, "Front camera", sizeof(vdi->name));
        }
        /* Get supported sizes */
       uint32_t size;
        Camera_Profile **profiles = f->recevider->getPreviewProfileByIndex(i,&size);
        if (profiles) {
            PJ_LOG(3, (THIS_FILE, "Camera_Profile size:%d",size));	
            adi->sup_size_cnt = size;
            adi->sup_size =
                (pjmedia_rect_size *)pj_pool_calloc(f->dev_pool, adi->sup_fps_cnt, sizeof(adi->sup_size[0]));
            for (int j = 0; j < size; j++) {
                adi->sup_size[j].w = profiles[j]->size.width;
                adi->sup_size[j].h = profiles[j]->size.height;
                /* Get supported formats */    
                if(profiles[j]->format == CAMERA_FORMAT_YUV_420_SP)
                {
                pjmedia_format_init_video(&vdi->fmt[vdi->fmt_cnt++], PJMEDIA_FORMAT_I420, adi->sup_size[j].w,
                                          adi->sup_size[j].h, DEFAULT_FPS, 1);
                /* Portrait video */
                pjmedia_format_init_video(&vdi->fmt[vdi->fmt_cnt++], PJMEDIA_FORMAT_I420, adi->sup_size[j].h,
                                          adi->sup_size[j].w, DEFAULT_FPS, 1);
                }
            }
        }

        /* If this is front camera, set it as first/default (if not yet) */
        if (facing == 2) {
            if (!found_front && f->dev_count > 0) {
                /* Swap this front cam info with one whose idx==0 */
                ohos_video_dev_info tmp_adi;
                pj_memcpy(&tmp_adi, &f->dev_info[0], sizeof(tmp_adi));
                pj_memcpy(&f->dev_info[0], adi, sizeof(tmp_adi));
                pj_memcpy(adi, &tmp_adi, sizeof(tmp_adi));
                f->dev_info[0].info.id = 0;
                f->dev_info[f->dev_count].info.id = f->dev_count;
            }
            found_front = PJ_TRUE;
        }

        f->dev_count++;
    }

    PJ_LOG(4, (THIS_FILE, "ohos video capture initialized with %d device(s):", f->dev_count));
    for (i = 0; i < f->dev_count; i++) {
        ohos_video_dev_info *adi = &f->dev_info[i];
        char tmp_str[2048], *p;
        int j, plen, slen;
        PJ_LOG(4, (THIS_FILE, "%2d: %s", i, f->dev_info[i].info.name));

        /* Print supported formats */
        p = tmp_str;
        plen = sizeof(tmp_str);
        for (j = 0; j < adi->info.fmt_cnt; j++) {
            char tmp_str2[5];
            const pjmedia_video_format_detail *vfd = pjmedia_format_get_video_format_detail(&adi->info.fmt[j], 0);
            pjmedia_fourcc_name(adi->info.fmt[j].id, tmp_str2);
            slen = pj_ansi_snprintf(p, plen, "%s/%dx%d ", tmp_str2, vfd->size.w, vfd->size.h);
            if (slen < 0 || slen >= plen)
                break;
            plen -= slen;
            p += slen;
        }
        PJ_LOG(4, (THIS_FILE, "     supported format = %s", tmp_str));
    }
on_return:
    return status;
}


/* API: get number of devices */
static unsigned ohos_factory_get_dev_count(pjmedia_vid_dev_factory *ff) {
    ohos_video_factory *f = (ohos_video_factory *)ff;
    PJ_LOG(3, (THIS_FILE, "ohos_factory_get_dev_count dev_count:%d",f->dev_count));
    return f->dev_count;
}

/* API: get device info */
static pj_status_t ohos_factory_get_dev_info(pjmedia_vid_dev_factory *f, unsigned index, pjmedia_vid_dev_info *info) {
    ohos_video_factory *cf = (ohos_video_factory *)f;
    PJ_LOG(3, (THIS_FILE, "ohos_factory_get_dev_info"));
    PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);
    pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info));

    return PJ_SUCCESS;
}

/* API: create default device parameter */
static pj_status_t ohos_factory_default_param(pj_pool_t *pool, pjmedia_vid_dev_factory *f, unsigned index,
                                             pjmedia_vid_dev_param *param) {
    ohos_video_factory *cf = (ohos_video_factory *)f;
    ohos_video_dev_info *di = &cf->dev_info[index];
    PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV);

    PJ_UNUSED_ARG(pool);
    pj_bzero(param, sizeof(*param));
    param->dir = PJMEDIA_DIR_CAPTURE;
    param->cap_id = index;
    param->rend_id = PJMEDIA_VID_INVALID_DEV;
    param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
    param->clock_rate = DEFAULT_CLOCK_RATE;
    pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));

    return PJ_SUCCESS;
}

/* API: create stream */
static pj_status_t ohos_factory_create_stream(pjmedia_vid_dev_factory *ff, pjmedia_vid_dev_param *param,
                                             const pjmedia_vid_dev_cb *cb, void *user_data,
                                             pjmedia_vid_dev_stream **p_vid_strm) {
    ohos_video_factory *f = (ohos_video_factory *)ff;
    pj_pool_t *pool;
    ohos_video_stream *strm;
    ohos_video_dev_info *adi;
    const pjmedia_video_format_detail *vfd;
    const pjmedia_video_format_info *vfi;
    pjmedia_video_apply_fmt_param vafp;
    pj_status_t status = PJ_SUCCESS;

    PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL);
    PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO &&
                         param->dir == PJMEDIA_DIR_CAPTURE,
                     PJ_EINVAL);

    pj_bzero(&vafp, sizeof(vafp));
    adi = &f->dev_info[param->cap_id];
    vfd = pjmedia_format_get_video_format_detail(&param->fmt, PJ_TRUE);
    vfi = pjmedia_get_video_format_info(NULL, param->fmt.id);

    if (!vfi)
        return PJMEDIA_EVID_BADFORMAT;

    vafp.size = vfd->size;
    if (vfi->apply_fmt(vfi, &vafp) != PJ_SUCCESS)
        return PJMEDIA_EVID_BADFORMAT;

    if(!f->recevider) {
        ohos_factory_init(ff);
    }

    /* Create and Initialize stream descriptor */
    pool = pj_pool_create(f->pf, "ohos-dev", 512, 512, NULL);
    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);

    strm = PJ_POOL_ZALLOC_T(pool, ohos_video_stream);
    pj_memcpy(&strm->param, param, sizeof(*param));
    strm->pool = pool;
    strm->factory = f;
    pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
    strm->user_data = user_data;
    pj_memcpy(&strm->vafp, &vafp, sizeof(vafp));
    strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);

    pj_assert(vfi->plane_cnt > 1);
    strm->convert_to_i420 = 1;
    strm->convert_buf = (pj_uint8_t *)pj_pool_alloc(pool, vafp.plane_bytes[1]);

    /* Instantiate PjCamera */
    strm->cam_size.w = (vfd->size.w > vfd->size.h ? vfd->size.w : vfd->size.h);
    strm->cam_size.h = (vfd->size.w > vfd->size.h ? vfd->size.h : vfd->size.w);
    f->recevider->setWidth(strm->cam_size.w);
    f->recevider->setHeight(strm->cam_size.h);

    /* Video orientation.
     * If we send in portrait, we need to set up orientation converter
     * as well.
     */
    f->recevider->setPjsipStream(strm);
    if ((param->flags & PJMEDIA_VID_DEV_CAP_ORIENTATION) || (vfd->size.h > vfd->size.w)) {
        if (param->orient == PJMEDIA_ORIENT_UNKNOWN)
            param->orient = PJMEDIA_ORIENT_NATURAL;
        ohos_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_ORIENTATION, &param->orient);
    }

    /* Success */
    if (status == PJ_SUCCESS) {
        strm->base.op = &stream_op;
        *p_vid_strm = &strm->base;
    }
    PJ_LOG(3, (THIS_FILE, "ohos_factory_create_stream end status:%d",status));
    return status;
}


/* API: Get stream info. */
static pj_status_t ohos_stream_get_param(pjmedia_vid_dev_stream *s, pjmedia_vid_dev_param *pi) {
    ohos_video_stream *strm = (ohos_video_stream *)s;
    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
    pj_memcpy(pi, &strm->param, sizeof(*pi));
    if (ohos_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, &pi->window) == PJ_SUCCESS) {
        pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
    }
    return PJ_SUCCESS;
}

/* API: get capability */
static pj_status_t ohos_stream_get_cap(pjmedia_vid_dev_stream *s, pjmedia_vid_dev_cap cap, void *pval) {
    PJ_UNUSED_ARG(s);
    PJ_UNUSED_ARG(cap);
    PJ_UNUSED_ARG(pval);
    return PJMEDIA_EVID_INVCAP;
}

static pj_status_t ohos_stream_set_cap(pjmedia_vid_dev_stream *s, pjmedia_vid_dev_cap cap, const void *pval) {
    ohos_video_stream *strm = (ohos_video_stream *)s;
    pj_bool_t with_attach;
    pj_status_t status = PJ_SUCCESS;
    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
    PJ_LOG(3, (THIS_FILE, "ohos_stream_set_cap cap:%d",cap));
    switch (cap) {
        case PJMEDIA_VID_DEV_CAP_SWITCH: {
            pjmedia_vid_dev_switch_param *p = (pjmedia_vid_dev_switch_param *)pval;
            ohos_video_dev_info *adi;
            int res;

            /* Just return if current and target device are the same */
            if (strm->param.cap_id == p->target_id)
                return PJ_SUCCESS;

            /* Verify target capture ID */
            if (p->target_id < 0 || p->target_id >= strm->factory->dev_count)
                return PJ_EINVAL;

            /* Ok, let's do the switch */
            adi = &strm->factory->dev_info[p->target_id];
            PJ_LOG(4, (THIS_FILE, "Switching camera to %s..", adi->info.name));

            strm->factory->recevider->setCameraIndex(adi->dev_idx);
            res = strm->factory->recevider->StartCamera();
            if (res != 0) {
                PJ_LOG(3, (THIS_FILE, "Failed to switch camera (err=%d)", res));
                status = PJMEDIA_EVID_SYSERR;
            } else {
                strm->param.cap_id = p->target_id;
                /* If successful, set the orientation as well */
                ohos_stream_set_cap(s, PJMEDIA_VID_DEV_CAP_ORIENTATION, &strm->param.orient);
            }
            break;
        }
        case PJMEDIA_VID_DEV_CAP_ORIENTATION: {
            pjmedia_orient orient = *(pjmedia_orient *)pval;
            pjmedia_orient eff_ori;
            ohos_video_dev_info *adi;

            pj_assert(orient >= PJMEDIA_ORIENT_UNKNOWN && orient <= PJMEDIA_ORIENT_ROTATE_270DEG);

            if (orient == PJMEDIA_ORIENT_UNKNOWN)
                return PJ_EINVAL;

            pj_memcpy(&strm->param.orient, pval, sizeof(strm->param.orient));

            if (!strm->conv.conv) {
                status = pjmedia_vid_dev_conv_create_converter(&strm->conv, strm->pool, &strm->param.fmt, strm->cam_size,
                                                            strm->param.fmt.det.vid.size, PJ_TRUE, PJ_TRUE);

                if (status != PJ_SUCCESS)
                    return status;
            }

            eff_ori = strm->param.orient;
            adi = &strm->factory->dev_info[strm->param.cap_id];
            /* Normalize the orientation for back-facing camera */
            if (!adi->facing) {
                if (eff_ori == PJMEDIA_ORIENT_ROTATE_90DEG)
                    eff_ori = PJMEDIA_ORIENT_ROTATE_270DEG;
                else if (eff_ori == PJMEDIA_ORIENT_ROTATE_270DEG)
                    eff_ori = PJMEDIA_ORIENT_ROTATE_90DEG;
            }
            pjmedia_vid_dev_conv_set_rotation(&strm->conv, eff_ori);

            PJ_LOG(4, (THIS_FILE, "Video capture orientation set to %d", strm->param.orient));
            break;
        }
        default:
            status = PJMEDIA_EVID_INVCAP;
            break;
    }
    return status;
}

/* API: Start stream. */
static pj_status_t ohos_stream_start(pjmedia_vid_dev_stream *s) {
    ohos_video_stream *strm = (ohos_video_stream *)s;
    pj_status_t status = PJ_SUCCESS;
    PJ_LOG(4, (THIS_FILE, "Starting Harmony camera stream"));
    auto res = strm->factory->recevider->StartCamera();
    if (res != 0) {
        PJ_LOG(3, (THIS_FILE, "Failed to start camera (err=%d)", res));
        status = PJMEDIA_EVID_SYSERR;
        goto on_return;
    }
    strm->is_running = PJ_TRUE;
    PJ_LOG(4, (THIS_FILE, "Starting ohos camera stream end status:%d",status));
on_return:
    return status;
}

/* API: Stop stream. */
static pj_status_t ohos_stream_stop(pjmedia_vid_dev_stream *s) {
    ohos_video_stream *strm = (ohos_video_stream *)s;
    PJ_LOG(4, (THIS_FILE, "Stopping ohos camera stream start"));
    pj_status_t status = PJ_SUCCESS;
    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);
    strm->factory->recevider->StopCamera();
    strm->is_running = PJ_FALSE;
    
    PJ_LOG(4, (THIS_FILE, "Stopping ohos camera stream"));
    return status;
}

/* API: Destroy stream. */
static pj_status_t ohos_stream_destroy(pjmedia_vid_dev_stream *s) {
    ohos_video_stream *strm = (ohos_video_stream *)s;
    PJ_LOG(3, (THIS_FILE, "ohos_stream_destroy"));
    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);

    if (strm->is_running)
        ohos_stream_stop(s);
    pjmedia_vid_dev_conv_destroy_converter(&strm->conv);
    if (strm->pool)
        pj_pool_release(strm->pool);
    
    delete strm->factory->recevider;
    strm->factory->recevider = nullptr;
    PJ_LOG(4, (THIS_FILE, "Harmony camera stream destroyed"));
    return PJ_SUCCESS;
}

#endif  /* PJMEDIA_VIDEO_DEV_HAS_OHOS */